/* SPDX-License-Identifier: GPL-2.0 */
/* X-SPDX-Copyright-Text: (c) Copyright 2007-2020 Xilinx, Inc. */
#include "efch.h"
#include <ci/efrm/driver_private.h> /* FIXME for efrm_rm_table */
#include <ci/efrm/nic_table.h>
#include <ci/efch/op_types.h>
#include "linux_char_internal.h"
#include "char_internal.h"
#include "efch_intf_ver.h"


/* Versions of the user-kernel interface that this driver is compatible
 * with.  Each time we settle on a stable interface, the version string is
 * entered into this table.  The index in this table is the
 * interface-version-ID (intf_ver_id), which is used to identify the
 * particular interface version.
 */
static const char* supported_intf_vers[] = {
  /* openonload-201109-u2, enterpriseonload-2.0.x.y */
  "b7722b6c157e5218c23d18a1adf77b66",
  /* openonload-201205, openonload-201205-p* */
  "d822c8b2d991d004af52010af1743ced",
  /* openonload-201205-u1, enterpriseonload-2.1.0.0 */
  "52c36f3964131195d601b07e866402fc",
  /* openonload-201210-u1, enterpriseonload-2.1.0.[1,2,3] */
  "24163488b5f84fc1b5b6c3a6aca83ce8",
  /* enterpriseonload-2.1.0.[4,5,6,7] */
  "11291f81239e4e12cf2597cfddc6322b",
  /* enterpriseonload-2.1.0.8 */
  "0d12a0e5a211a728d1165311602f4f32",
  /* openonload-201210-u[2,3] */
  "cc8e837f6e5ee9090ef4f0eac9bbc42c",
  /* openonload-201310 */
  "5f23f207329605c65df784feea258f0b",
  /* openonload-201310-u1 */
  "9ae7480c36d4fce1d77949dad9d60af7",
  /* openonload-201310-u[2,3], enterpriseonload-3.0.0.[0,1,2] */
  "cb65bda12f57797a914911e51513c8e6",
  /* openonload-201405 */
  "1cfb278bd5b7fa0b7fdb631cecb602bb",
  /* openonload-201405-u1, openonload-201405-u2 */
  "1518b4f7ec6834a578c7a807736097ce",
  /* openonload-201502, openonload-201502-u1 */
  /* NB. ef_vi in these versions pretended to be 201405-u1 as the API
   * change was in fact both forward and backward compatible */
  "c4122121098b174cc48ef7e56c792b4a",
  /* This version -- autogenerated from header. */
  EFCH_INTF_VER,
};


efch_resource_ops *efch_ops_table[] = {
  NULL,
  &efch_vi_ops,
  &efch_vi_set_ops,
  NULL,
  &efch_memreg_ops,
  &efch_pd_ops,
  &efch_pio_ops,
  NULL,
  &efch_efct_rxq_ops,
};


void
efch_resource_dump(efch_resource_t* rs)
{
  ci_assert(rs);

  if (rs->rs_base != NULL)
    ci_log("efch_resource_dump: %d "EFRM_RESOURCE_FMT" i=%d",
           rs->rs_base->rs_type, EFRM_RESOURCE_PRI_ARG(rs->rs_base),
           rs->rs_base->rs_instance);
  if( rs->rs_ops->rm_dump )
    rs->rs_ops->rm_dump(rs->rs_base, NULL, "");
}


int efch_lookup_rs(int fd, efch_resource_id_t rs_id, int rs_type,
                   struct efrm_resource **rs_out)
{
  ci_private_char_t *priv;
  efch_resource_t *rs;
  struct file *file;
  int rc;

  if ((file = fget(fd)) == NULL)
    return -EBADF;
  if (file->f_op == &ci_char_fops) {
    priv = file->private_data;
    rc = efch_resource_id_lookup(rs_id, &priv->rt, &rs);
    if (rc == 0) {
      if (rs->rs_base->rs_type == rs_type) {
        efrm_resource_ref(rs->rs_base);
        *rs_out = rs->rs_base;
      }
      else
        rc = -EINVAL;
      efch_resource_put(rs);
    }
  }
  else
    rc = -EINVAL;
  fput(file);
  return rc;
}
EXPORT_SYMBOL(efch_lookup_rs);


static void free_resource(efch_resource_t *rs)
{
  if (rs->rs_ops->rm_free != NULL)
    rs->rs_ops->rm_free(rs);
  if (rs->rs_base != NULL)
    efrm_resource_release(rs->rs_base);
  CI_DEBUG_ZERO(rs);
  ci_free(rs);
}


static int find_intf_ver(const char* ver)
{
  int n_vers = sizeof(supported_intf_vers) / sizeof(supported_intf_vers[0]);
  int i;
  for( i = 0; i < n_vers; ++i )
    if( strcmp(ver, supported_intf_vers[i]) == 0 )
      return i;
  return -1;
}


int
efch_resource_alloc(ci_resource_table_t* rt, ci_resource_alloc_t* alloc)
{
  efch_resource_t* rs;
  efch_resource_ops* ops;
  char intf_ver[EFCH_INTF_VER_LEN + 1];
  int rc, intf_ver_id;

  const struct xa_limit id_limit = {.max = EFRM_RESOURCE_MAX_PER_FD, .min = 0};
  ci_assert(alloc);
  ci_assert(rt);

  ci_assert(sizeof(intf_ver) >= sizeof(alloc->intf_ver));
  memcpy(intf_ver, alloc->intf_ver, sizeof(alloc->intf_ver));
  intf_ver[EFCH_INTF_VER_LEN] = '\0';
  if( (intf_ver_id = find_intf_ver(intf_ver)) < 0 ) {
    ci_log("%s: ERROR: incompatible interface version", __FUNCTION__);
    ci_log("%s: user_ver=%s driver_ver=%s",
           __FUNCTION__, intf_ver, EFCH_INTF_VER);
    return -ELIBACC;
  }

  if( alloc->ra_type >= EFRM_RESOURCE_NUM ||
      (ops = efch_ops_table[alloc->ra_type]) == NULL ) {
    ci_log("%s: unsupported type %d", __FUNCTION__, alloc->ra_type);
    return -EOPNOTSUPP;
  }

  rs = ci_alloc(sizeof(*rs));
  if (rs == NULL)
    return -ENOMEM;

  rc = ops->rm_alloc(alloc, rt, rs, intf_ver_id);
  if( rc < 0 ) {
    /* Squash log for EOPNOTSUPP as this indicates unsupported feature
     * and caller should try again rather than a genuine error 
     */
    if( rc != -EOPNOTSUPP )
      EFCH_WARN("%s: did not allocate %s (%d)", __FUNCTION__, 
                EFRM_RESOURCE_NAME(alloc->ra_type),rc);
    ci_free(rs);
    return rc;
  }
  rs->rs_ops = ops;

  /* The table holds one reference, released in efch_resource_free */
  refcount_set(&rs->ref_count, 1);

  /* Insert it into the ci_resource_table_t resource table */
  rc = xa_alloc(&rt->table, &alloc->out_id.index, rs, id_limit, GFP_KERNEL);
  if (rc == 0) {
    EFCH_TRACE("%s: allocated type=%s "EFCH_RESOURCE_ID_FMT, __FUNCTION__,
               EFRM_RESOURCE_NAME(alloc->ra_type),
               EFCH_RESOURCE_ID_PRI_ARG(alloc->out_id));
    return 0;
  }

  free_resource(rs);
  return rc;
}


void efch_resource_free(efch_resource_id_t id, ci_resource_table_t* rt)
{
  efch_resource_t* rs = xa_erase(&rt->table, id.index);

  if( rs != NULL ) {
    EFCH_TRACE("%s: erased resource " EFCH_RESOURCE_ID_FMT " -> %p",
               __func__, EFCH_RESOURCE_ID_PRI_ARG(id), rs);

    /* Ensure efch_resource_id_lookup hasn't accessed the resource without
     * taking a reference */
    synchronize_rcu();
    efch_resource_put(rs);
  }
}

void efch_resource_free_all(ci_resource_table_t* rt)
{
  unsigned long i;
  efch_resource_t* rs;

  xa_for_each(&rt->table, i, rs) {
    efch_resource_id_t id = efch_make_resource_id(i);
    int refs = refcount_read(&rs->ref_count) - 1;

    if( refs != 0 )
      EFCH_WARN("%s: resource " EFCH_RESOURCE_ID_FMT
                " may be leaked with %d refs",
                __func__, EFCH_RESOURCE_ID_PRI_ARG(id), refs);

    EFCH_TRACE("%s: erased resource " EFCH_RESOURCE_ID_FMT " -> %p",
               __func__, EFCH_RESOURCE_ID_PRI_ARG(id), rs);

    efch_resource_put(rs);
  }

  xa_destroy(&rt->table);
}

void efch_resource_put(efch_resource_t* rs)
{
  if( refcount_dec_and_test(&rs->ref_count) ) {
    free_resource(rs);
    EFCH_TRACE("%s: freed resource %p", __func__, rs);
  }
}

int
efch_resource_op(ci_resource_table_t* rt,
                 ci_resource_op_t* op, int* copy_out)
{
  efch_resource_t* rs;
  int rc;

  rc = efch_resource_id_lookup(op->id, rt, &rs);
  if( rc < 0 ) {
    EFCH_ERR("%s: ERROR: id="EFCH_RESOURCE_ID_FMT" op=0x%x rc=%d",
             __FUNCTION__, EFCH_RESOURCE_ID_PRI_ARG(op->id), op->op, rc);
    goto done;
  }

  if (op->op != CI_RSOP_RX_BUFFER_POST)
    EFCH_TRACE("%s: id="EFCH_RESOURCE_ID_FMT" op=0x%x",
               __FUNCTION__, EFCH_RESOURCE_ID_PRI_ARG(op->id), op->op);
  if (op->op == CI_RSOP_DUMP) {
    efch_resource_dump(rs);
    rc = 0;
  } else if (rs->rs_ops->rm_rsops == NULL) {
    rc = -EOPNOTSUPP;
  } else {
    rc = rs->rs_ops->rm_rsops(rs, rt, op, copy_out);
  }
  efch_resource_put(rs);

  /* We isolate the buffer post resource OP because its very common for ef10ct
   * and logging to dmesg for every call slows things down significantly. */
  if (rc != 0 || op->op != CI_RSOP_RX_BUFFER_POST)
    EFCH_TRACE("%s: id="EFCH_RESOURCE_ID_FMT" op=0x%x rc=%d",
               __FUNCTION__, EFCH_RESOURCE_ID_PRI_ARG(op->id), op->op, rc);
done:
  return rc;
}

int efch_filter_add(ci_resource_table_t* rt, ci_filter_add_t* filter_add,
                    int* copy_out)
{
  efch_resource_t* rs;
  int rc;

  rc = efch_resource_id_lookup(filter_add->in.res_id, rt, &rs);
  if( rc < 0 ) {
    EFCH_ERR("%s: ERROR: id="EFCH_RESOURCE_ID_FMT" rc=%d",
             __FUNCTION__, EFCH_RESOURCE_ID_PRI_ARG(filter_add->in.res_id), rc);
    return rc;
  }

  EFCH_TRACE("%s: id="EFCH_RESOURCE_ID_FMT, __FUNCTION__,
             EFCH_RESOURCE_ID_PRI_ARG(filter_add->in.res_id));

  rc = efch_vi_filter_add(rs, filter_add, copy_out);
  efch_resource_put(rs);
  EFCH_TRACE("%s: id="EFCH_RESOURCE_ID_FMT" rc=%d", __FUNCTION__,
             EFCH_RESOURCE_ID_PRI_ARG(filter_add->in.res_id), rc);
  return rc;
}
